package org.ourgrid.common.executor.kvm;

import static org.ourgrid.common.executor.vserver.ProcessUtil.buildAndRunProcess;
import static org.ourgrid.common.executor.vserver.ProcessUtil.parseCommand;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.ourgrid.common.executor.ExecutorException;
import org.ourgrid.common.executor.ExecutorResult;
import org.ourgrid.common.executor.FolderBasedSandboxedUnixEnvironmentUtil;
import org.ourgrid.common.executor.SandBoxEnvironment;
import org.ourgrid.common.executor.config.ExecutorConfiguration;
import org.ourgrid.common.executor.config.KVMExecutorConfiguration.PROPERTIES;
import org.ourgrid.worker.WorkerConstants;

import br.edu.ufcg.lsd.commune.container.logging.CommuneLogger;

/**
 * 
 * 
 * 
 * @author Carla Souza, carla.asouza@gmail.com
 * @since Out 2009
 */
public class KVMSandBoxEnvironment implements SandBoxEnvironment {


	private String vmName;
	private CommuneLogger logger;
    private String host_storage_path;
    private String host_playpen_path;
    private String playPenVmRoot;
	private String storageVmRoot;
	private File ourGridAppFile; //FIXME Serve para q?	
	private File stdOutput;
	private File errorOutput;
	private File exitValue;
	private ExecutorConfiguration configuration;
	private List<String> startvmCmd;
	private List<String> stopvmCmd;
	private List<String> beginAllocationCmd;
	private List<String> createDevCmd;
	private List<String> umountDevCmd;
	private List<String> vmStatusCmd;
	private List<String> killVmCmd;
	private List<String> getFilesFromDevCmd;
	
	private final FolderBasedSandboxedUnixEnvironmentUtil unixFolderUtil = new FolderBasedSandboxedUnixEnvironmentUtil();
	private final String bootScript = "cmd_from_host.sh";

	/**
	 * 
	 * @param logger
	 */
	public KVMSandBoxEnvironment(CommuneLogger logger) {
		this.logger = logger;
	}

	/**
	 * 
	 */
	public void setConfiguration(ExecutorConfiguration executorConfiguration) {
		this.vmName = executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.VM_NAME.toString());
		this.startvmCmd = parseCommand(executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.START_VM_COMMAND.toString()));
		this.stopvmCmd = parseCommand(executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.STOP_VM_COMMAND.toString()));
		this.beginAllocationCmd = parseCommand(executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.BEGIN_ALLOCATION_COMMAND.toString()));
		this.createDevCmd = parseCommand(executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.CREATE_DEVICE_COMMAND.toString()));
		this.umountDevCmd = parseCommand(executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.UMOUNT_DEVICE_COMMAND.toString()));
		this.vmStatusCmd = parseCommand(executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.STATUS_VM_COMMAND.toString()));
		this.getFilesFromDevCmd = parseCommand(executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.GET_FILES_DEVICE_COMMAND.toString()));
		this.killVmCmd = parseCommand(executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.KILL_COMMAND.toString()));
		this.playPenVmRoot = executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.VM_PLAYPEN.toString());
        this.storageVmRoot = executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.VM_STORAGE.toString());
        this.host_storage_path = executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.HOST_STORAGE.toString());
        this.host_playpen_path = executorConfiguration.getProperty(WorkerConstants.PREFIX + PROPERTIES.HOST_PLAYPEN.toString());

		this.configuration = executorConfiguration;
	}
	
	/**
	 * This method does nothing because the VM can only start after send command to it.
	 */
	public void initSandboxEnvironment(Map<String, String> envVars)
			throws ExecutorException {
		//do nothing
	}
	
	/**
	 * Descompacta e cria o device
	 */
	public void beginAllocation() throws ExecutorException {
		try {
			this.stdOutput = 
				new File(host_playpen_path, configuration.getProperty(WorkerConstants.PREFIX + PROPERTIES.APP_STDOUT_FILE_NAME.toString()));
			this.errorOutput = 
				new File(host_playpen_path, configuration.getProperty(WorkerConstants.PREFIX + PROPERTIES.APP_STDERROR_FILE_NAME.toString()));
			this.exitValue = 
				new File(host_playpen_path, configuration.getProperty(WorkerConstants.PREFIX + PROPERTIES.TERMINATION_FILE_NAME.toString()));
	
		} catch (NullPointerException e) {
			throw new ExecutorException(e);
		}

		File variablesTmp = new File(host_playpen_path + File.separator + "variables" );
		try {
			variablesTmp.delete();
			variablesTmp.createNewFile();
			FileWriter appendedFile = new FileWriter(variablesTmp, true);
			appendedFile.write("export STORAGE=" + storageVmRoot+"\n");
			appendedFile.write("export PLAYPEN=" + playPenVmRoot+"\n");
			appendedFile.write("export OUTPUT=" + stdOutput.getName()+"\n");
			appendedFile.write("export ERROR=" + errorOutput.getName()+"\n");
			appendedFile.write("export EXIT_VALUE=" + exitValue.getName()+"\n");
			appendedFile.flush();
			appendedFile.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		/** Unzip (is this really necessary?) */
//		buildAndRunProcess(createBeginAllocationCommand(), "Could not begin allocation");

		/** Mount device */
		buildAndRunProcess(mountDeviceStorage(), "Could not mount device"); 
	}

	/**
	 * Unzip worker files
	 * Script not yet implemented
	 * @return
	 */
	private List<String> createBeginAllocationCommand() {
		List<String> beginAlloc = new LinkedList<String>(beginAllocationCmd);
		beginAlloc.add(vmName);
		beginAlloc.add(host_storage_path);
		return beginAlloc;
	}
	
	/**
	 * Starts and execute
	 * 
	 * @param dirName
	 * @param command
	 * @param envVars
	 * @throws ExecutorException
	 */
	public void executeRemoteCommand(String dirName, String command, Map<String, String> envVars) throws ExecutorException {
		logger.info( "Asked to run remote command " + command);

		Map<String, String> clone = new HashMap<String, String>();
		clone.putAll(envVars);
		clone.remove(WorkerConstants.ENV_PLAYPEN);
		clone.remove(WorkerConstants.ENV_STORAGE);
		clone.put(WorkerConstants.ENV_PLAYPEN, playPenVmRoot);
		clone.put(WorkerConstants.ENV_STORAGE, storageVmRoot);
		
		/** Create cmd script */
		File remoteScriptTmp = unixFolderUtil.createScript( command, host_playpen_path, null );
		File remoteScript = new File (host_playpen_path  + File.separator + bootScript);
		remoteScriptTmp.renameTo(remoteScript);
		remoteScript.setExecutable(true);
		remoteScriptTmp.delete();
		// Append halt command 
		try {
			FileWriter appendedFile = new FileWriter(remoteScript, true);
			appendedFile.write("halt\n");
			appendedFile.flush();
			appendedFile.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		/** Umount device. Copy storage and playpen to vm */
		buildAndRunProcess(umountDeviceStorage(), "Could not mount device"); 
		
		/** Start vm */
		buildAndRunProcess(createInitCommand(), "Could not start VM");
		
	}
	
	/*
	 * 
	 */
	private List<String> createInitCommand() {
        List<String> initCommand = new LinkedList<String>(startvmCmd);
        initCommand.add(vmName);
        return initCommand;
    }
	
	/*
	 * 
	 */
	private List<String> mountDeviceStorage() {
		List<String> mountCmd = new LinkedList<String>(createDevCmd);
		mountCmd.add(vmName);
		return mountCmd;
	}
	
	/*
	 * 
	 */
	private List<String> umountDeviceStorage() {
		List<String> mountCmd = new LinkedList<String>(umountDevCmd);
		mountCmd.add(host_storage_path);
		mountCmd.add(host_playpen_path);
		return mountCmd;
	}

	/**
	 * 1. Mount and get files from device
	 * 2. Copy files from guest storage and playpen to host storage and playpen
	 */
	public ExecutorResult getResult() throws ExecutorException {
		logger.debug("Getting result of execution...");
		ExecutorResult result = new ExecutorResult();

		transferDev(); 
		File HostStdOutput = new File(host_playpen_path + File.separator + stdOutput.getName());
		File HostErrorOutput = new File(host_playpen_path + File.separator + errorOutput.getName());
		File HostExitValue = new File(host_playpen_path + File.separator + exitValue.getName());

		try {
			unixFolderUtil.catchOutputFromFile(result, HostStdOutput, HostErrorOutput, HostExitValue);
			unixFolderUtil.catchOutputFromFile(result, stdOutput, errorOutput, exitValue);
		} catch (Exception e) {
			throw new ExecutorException("Unable to catch output ", e);
		}

		logger.debug("Finished getResult. Single Execution released.");
		return result;
		
	}
	
	private void transferDev() throws ExecutorException {
		List<String> stop = new LinkedList<String>(getFilesFromDevCmd);
		stop.add(vmName);
		stop.add(host_storage_path);
		stop.add(host_playpen_path);
		stop.add(storageVmRoot);
		stop.add(playPenVmRoot);
		buildAndRunProcess(stop, "Could not stop VM.");
	}

	/**
	 * 
	 */
	public void kill() throws ExecutorException {
		List<String> stop = new LinkedList<String>(killVmCmd);
		stop.add(vmName);
		buildAndRunProcess(stop, "Could not stop VM.");
	}

	/**
	 * 
	 */
	public void shutDownSandBoxEnvironment() throws ExecutorException {
		shutDown(); //or kill?
	}
	
	/**
	 * 
	 * @throws ExecutorException
	 */
	private void shutDown() throws ExecutorException {
		List<String> stop = new LinkedList<String>(stopvmCmd);
		stop.add(vmName);
		buildAndRunProcess(stop, "Could not stop VM.");
	}
	
	/**
	 * 
	 */
	public void finishExecution() throws ExecutorException {
		//do nothing
	}
	
	/**
	 * 
	 * @return
	 */
	public boolean hasExecutionFinished() throws ExecutorException {
		return !isSandBoxUp();
	}
	
	/**
	 * FIXME: change this to private. Its public for testing purprose
	 * @return
	 * @throws ExecutorException
	 */
	public boolean isSandBoxUp() throws ExecutorException {
		return buildAndRunProcess(createVerifyCommand(), "Could not verify VM state: " + vmName);
	}
	
	private List<String> createVerifyCommand() {
        List<String> verify = new LinkedList<String>(vmStatusCmd);
        verify.add(vmName);
        return verify;
    }

}
